﻿/*******************************************************************************************
 * Copyright (c) 2006-2009 Alex Robson, Rob Simmons, Craig Israel, and Rachel Twyford
 *
 * Use of this software in any form requires that you have both 
 * read and agreed to the following terms:
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the "Software"), 
 * to deal in the Software without restriction, including without limitation 
 * the rights to use, copy, modify, merge, publish, distribute, sublicense, 
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions: 
 *
 * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright notice, 
 * this list of conditions and the following disclaimer in the 
 * documentation and/or other materials provided with the distribution.
 * Neither the name of Nvigorate nor the names of its contributors may 
 * be used to endorse or promote products derived from this software without 
 * specific prior written permission. 
 *
 * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
 * DISCLAIMED. IN NO EVENT SHALL ANY CONTRIBUTOR BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * ****************************************************************************************/

using System;
using System.Globalization;
using System.Collections.Generic;
using System.Text.RegularExpressions;

namespace Nvigorate.Common
{
    /// <summary>
    /// Nvigorate.Number is a universal numeric construct which shifts its accuracy up (or down) to
    /// the required capacity and precision by storing the actual value in different internal type values.
    /// 
    /// This class was designed to get around the limitation that there is no way to constrain a generic type
    /// to a numeric.
    /// </summary>
    public struct Number : IConvertible, IComparable
    {
        #region Fields

        private short _short;
        private int _integer;
        private long _long;

        private decimal _decimal;
        private float _single;
        private double _double;

        private Type _activeType;

        #endregion

        #region Conversion Factory Fun With Anonymous Functions



        #endregion

        #region Properties

        /// <summary>
        /// The actual numeric type that is currently represented by this instance.
        /// </summary>
        public Type ActiveType
        {
            get { return _activeType; }
            set { _activeType = value; }
        }

        public bool IsDecimal
        {
            get
            {
                if (_short + _integer + _long == 0)
                {
                    //Determines whether or not a decimal type is required
                    return Regex.IsMatch(this.ToString(), @"\d+(?=[.]((0+[1-9]+)|([1-9]+)))");
                }
                else
                {
                    return false;
                }
            }
        }

        public double @double
        {
            set
            {
                setDouble();
                _double = value;
            }
        }

        public short @short
        {
            set
            {
                setShort();
                _short = value;
            }
        }

        public int @integer
        {
            set
            {
                setInt();
                _integer = value;
            }
        }

        public long @long
        {
            set
            {
                setLong();
                _long = value;
            }
        }

        public decimal @decimal
        {
            set
            {
                setDecimal();
                _decimal = value;
            }
        }

        public float @single
        {
            set
            {
                setFloat();
                _single = value;
            }
        }

        #endregion

        #region Operators

        public static Number operator +(Number number1, Number number2)
        {
            Number number = 0;

            if (number1.IsDecimal || number2.IsDecimal)
            {
                Double d1 = number1;
                Double d2 = number2;
                Double value = d1 + d2;
                SetDecimalScale(ref number, value);
            }
            else
            {
                Int64 i1 = number1;
                Int64 i2 = number2;
                Int64 value = i1 + i2;
                SetIntegerScale(ref number, value);
            }

            return number;
        }

        public static Number operator -(Number number1, Number number2)
        {
            Number number = 0;

            if (number1.IsDecimal || number2.IsDecimal)
            {
                Double d1 = number1;
                Double d2 = number2;
                Double value = d1 - d2;
                SetDecimalScale(ref number, value);
            }
            else
            {
                Int64 i1 = number1;
                Int64 i2 = number2;
                Int64 value = i1 - i2;
                SetIntegerScale(ref number, value);
            }

            return number;
        }

        public static Number operator *(Number number1, Number number2)
        {
            Number number = 0;

            if (number1.IsDecimal || number2.IsDecimal)
            {
                Double d1 = number1;
                Double d2 = number2;
                Double value = d1 * d2;
                SetDecimalScale(ref number, value);
            }
            else
            {
                Int64 i1 = number1;
                Int64 i2 = number2;
                Int64 value = i1 * i2;
                SetIntegerScale(ref number, value);
            }

            return number;
        }

        public static Number operator /(Number number1, Number number2)
        {
            Number number = 0;

            if (number1.IsDecimal || number2.IsDecimal)
            {
                Double d1 = number1;
                Double d2 = number2;
                Double value = d1 / d2;
                SetDecimalScale(ref number, value);
            }
            else
            {
                Int64 i1 = number1;
                Int64 i2 = number2;
                Int64 value = i1 / i2;
                SetIntegerScale(ref number, value);
            }

            return number;
        }

        public static bool operator <(Number number1, Number number2)
        {
            return number1.CompareTo(number2) > 0;
        }

        public static bool operator >(Number number1, Number number2)
        {
            return number1.CompareTo(number2) < 0;
        }

        public static bool operator <=(Number number1, Number number2)
        {
            return number1.CompareTo(number2) >= 0;
        }

        public static bool operator >=(Number number1, Number number2)
        {
            return number1.CompareTo(number2) <= 0;
        }

        #endregion

        #region IConvertible Members

        public TypeCode GetTypeCode()
        {
            throw new NotImplementedException();
        }

        public bool ToBoolean(IFormatProvider provider)
        {
            throw new NotImplementedException();
        }

        public char ToChar(IFormatProvider provider)
        {
            throw new NotImplementedException();
        }

        public sbyte ToSByte(IFormatProvider provider)
        {
            throw new NotImplementedException();
        }

        public byte ToByte(IFormatProvider provider)
        {
            throw new NotImplementedException();
        }

        public short ToInt16(IFormatProvider provider)
        {
            GetValue<short>();
            return _short;
        }

        public ushort ToUInt16(IFormatProvider provider)
        {
            throw new NotImplementedException();
        }

        public int ToInt32(IFormatProvider provider)
        {
            GetValue<int>();
            return _integer;
        }

        public uint ToUInt32(IFormatProvider provider)
        {
            throw new NotImplementedException();
        }

        public long ToInt64(IFormatProvider provider)
        {
            GetValue<long>();
            return _long;
        }

        public ulong ToUInt64(IFormatProvider provider)
        {
            throw new NotImplementedException();
        }

        public float ToSingle(IFormatProvider provider)
        {
            GetValue<Single>();
            return _single;
        }

        public double ToDouble(IFormatProvider provider)
        {
            GetValue<Double>();
            return _double;
        }

        public decimal ToDecimal(IFormatProvider provider)
        {
            GetValue<decimal>();
            return _decimal;
        }

        public DateTime ToDateTime(IFormatProvider provider)
        {
            throw new NotImplementedException();
        }

        public string ToString(IFormatProvider provider)
        {
            switch (_activeType.Name)
            {
                case "Int16":
                    return _short.ToString(provider);
                case "Int32":
                    return _integer.ToString(provider);
                case "Int64":
                    return _long.ToString(provider);
                case "Single":
                    return _single.ToString(provider);
                case "Double":
                    return _double.ToString(provider);
                case "Decimal":
                    return _decimal.ToString(provider);
            }
            return "";
        }

        public object ToType(Type conversionType, IFormatProvider provider)
        {
            throw new NotImplementedException();
        }

        #endregion

        #region Type Conversion Operators

        public static implicit operator Number(short value)
        {
            return new Number(value);
        }

        public static implicit operator Number(int value)
        {
            return new Number(value);
        }

        public static implicit operator Number(long value)
        {
            return new Number(value);
        }

        public static implicit operator Number(float value)
        {
            return new Number(value);
        }

        public static implicit operator Number(double value)
        {
            return new Number(value);
        }

        public static implicit operator Number(decimal value)
        {
            return new Number(value);
        }

        public static implicit operator Number(string value)
        {
            return Number.Parse(value);
        }

        public static implicit operator short(Number value)
        {
            return value.ToInt16(new NumberFormatInfo());
        }

        public static implicit operator int(Number value)
        {
            return value.ToInt32(new NumberFormatInfo());
        }

        public static implicit operator long(Number value)
        {
            return value.ToInt64(new NumberFormatInfo());
        }

        public static implicit operator float(Number value)
        {
            return value.ToSingle(new NumberFormatInfo());
        }

        public static implicit operator double(Number value)
        {
            return value.ToDouble(new NumberFormatInfo());
        }

        public static implicit operator decimal(Number value)
        {
            return value.ToDecimal(new NumberFormatInfo());
        }
        #endregion

        #region Utility

        private void setShort()
        {
            _activeType = typeof(short);
        }

        private void setInt()
        {
            _activeType = typeof(int);
        }

        private void setLong()
        {
            _activeType = typeof(long);
        }

        private void setDecimal()
        {
            _activeType = typeof(decimal);
        }

        private void setFloat()
        {
            _activeType = typeof(float);
        }

        private void setDouble()
        {
            _activeType = typeof(double);
        }

        private static void SetIntegerScale(ref Number number, Int64 value)
        {
            if (value < (Int64)short.MaxValue)
            {
                number.@short = (short)value;
            }
            else if (value < (Int64)int.MaxValue)
            {
                number.@integer = (int)value;
            }
            else
            {
                number = value;
            }
        }

        private static void SetDecimalScale(ref Number number, Double value)
        {
            if (((Number)value).IsDecimal)
            {
                if (value < (Double) Decimal.MaxValue)
                {
                    number.@decimal = (decimal) value;
                }
                else if (value < Single.MaxValue)
                {
                    number.@single = (Single) value;
                }
                else
                {
                    number = value;
                }
            }
            else
            {
                SetIntegerScale(ref number, (Int64)value);
            }
        }

        private InvalidCastException CreateInvalidException(Type convertTo, Number value)
        {
            string message =
                string.Format("Casting {0} to {1} will cause precision loss!", value.ToString(), convertTo.Name);

            return new InvalidCastException(message);
        }
        #endregion

        #region Conversions
        private void GetValue<T>()
        {
            Type desiredType = typeof(T);

            if (!desiredType.Equals(_activeType))
            {
                switch (_activeType.Name)
                {
                    case "Int16":
                        ConvertInt16(desiredType);
                        break;
                    case "Int32":
                        ConvertInt32(desiredType);
                        break;
                    case "Int64":
                        ConvertInt64(desiredType);
                        break;
                    case "Single":
                        ConvertSingle(desiredType);
                        break;
                    case "Double":
                        ConvertDouble(desiredType);
                        break;
                    case "Decimal":
                        ConvertDecimal(desiredType);
                        break;
                }
            }
        }

        private void ConvertInt16(Type newType)
        {
            switch (newType.Name)
            {
                case "Int32":
                    _integer = _short;
                    break;
                case "Int64":
                    _long = _short;
                    break;
                case "Single":
                    _single = _short;
                    break;
                case "Double":
                    _double = _short;
                    break;
                case "Decimal":
                    _decimal = _short;
                    break;
            }
            _short = 0;

        }

        private void ConvertInt32(Type newType)
        {
            switch (newType.Name)
            {
                case "Int16":
                    if (Int16.MaxValue < _integer)
                        throw CreateInvalidException(newType, this);
                    _short = (Int16)_integer;
                    break;
                case "Int64":
                    _long = _integer;
                    break;
                case "Single":
                    _single = _integer;
                    break;
                case "Double":
                    _double = _integer;
                    break;
                case "Decimal":
                    _decimal = _integer;
                    break;
            }
            _integer = 0;
        }

        private void ConvertInt64(Type newType)
        {
            switch (newType.Name)
            {
                case "Int16":
                    if (Int16.MaxValue < _long)
                        throw CreateInvalidException(newType, this);
                    _short = (Int16)_long;
                    break;
                case "Int32":
                    if (Int32.MaxValue < _long)
                        throw CreateInvalidException(newType, this);
                    _integer = (int)_long;
                    break;
                case "Single":
                    _single = _long;
                    break;
                case "Double":
                    _double = _long;
                    break;
                case "Decimal":
                    _decimal = _long;
                    break;
            }
            _long = 0;
        }

        private void ConvertSingle(Type newType)
        {
            switch (newType.Name)
            {
                case "Int16":
                    if (Int16.MaxValue < _single)
                        throw CreateInvalidException(newType, this);
                    _short = (Int16)_single;
                    break;
                case "Int32":
                    if (Int32.MaxValue < _single)
                        throw CreateInvalidException(newType, this);
                    _integer = (int)_single;
                    break;
                case "Int64":
                    if (Int64.MaxValue < _single)
                        throw CreateInvalidException(newType, this);
                    _long = (Int64)_single;
                    break;
                case "Double":
                    _double = _single;
                    break;
                case "Decimal":
                    if ((Single)decimal.MaxValue < _single)
                        throw CreateInvalidException(newType, this);
                    _decimal = (decimal)_single;
                    break;
            }
            _single = 0;
        }

        private void ConvertDouble(Type newType)
        {
            switch (newType.Name)
            {
                case "Int16":
                    if (Int16.MaxValue < _double)
                        throw CreateInvalidException(newType, this);
                    _short = (Int16)_double;
                    break;
                case "Int32":
                    if (Int32.MaxValue < _double)
                        throw CreateInvalidException(newType, this);
                    _integer = (int)_double;
                    break;
                case "Int64":
                    if (Int64.MaxValue < _double)
                        throw CreateInvalidException(newType, this);
                    _long = (Int64)_double;
                    break;
                case "Single":
                    if (float.MaxValue < _double)
                        throw CreateInvalidException(newType, this);
                    _single = (Single)_double;
                    break;
                case "Decimal":
                    if ((Single)decimal.MaxValue < _double)
                        throw CreateInvalidException(newType, this);
                    _decimal = (decimal)_double;
                    break;
            }
            _double = 0;
        }

        private void ConvertDecimal(Type newType)
        {
            switch (newType.Name)
            {
                case "Int16":
                    if (Int16.MaxValue < _decimal)
                        throw CreateInvalidException(newType, this);
                    _short = (Int16)_decimal;
                    break;
                case "Int32":
                    if (Int32.MaxValue < _decimal)
                        throw CreateInvalidException(newType, this);
                    _integer = (int)_decimal;
                    break;
                case "Int64":
                    if (Int64.MaxValue < _decimal)
                        throw CreateInvalidException(newType, this);
                    _long = (Int64)_decimal;
                    break;
                case "Single":
                    _single = (Single)_decimal;
                    break;
                case "Double":
                    _double = (Double)_decimal;
                    break;
            }
            _decimal = 0;
        }
        #endregion
        
        #region Constructors

        public Number(Double value)
        {
            _short = 0;
            _integer = 0;
            _long = 0;
            _single = 0;
            _decimal = 0;

            _double = value;
            _activeType = typeof(Double);
        }

        public Number(Single value)
        {
            _short = 0;
            _integer = 0;
            _long = 0;
            _double = 0;
            _decimal = 0;

            _single = value;
            _activeType = typeof(Single);
        }

        public Number(decimal value)
        {
            _short = 0;
            _integer = 0;
            _long = 0;
            _single = 0;
            _double = 0;

            _decimal = value;
            _activeType = typeof(decimal);
        }

        public Number(Int64 value)
        {
            _short = 0;
            _integer = 0;
            _single = 0;
            _double = 0;
            _decimal = 0;

            _long = value;
            _activeType = typeof(long);
        }

        public Number(Int32 value)
        {
            _short = 0;
            _long = 0;
            _single = 0;
            _double = 0;
            _decimal = 0;

            _integer = value;
            _activeType = typeof(Int32);
        }

        public Number(short value)
        {
            _integer = 0;
            _long = 0;
            _single = 0;
            _double = 0;
            _decimal = 0;
            _short = value;

            _activeType = typeof(short);
        }

        public Number(object value)
        {
            if (value.GetType().Equals(typeof(Number)))
            {
                this = (Number)value;
                return;
            }

            _integer = 0;
            _long = 0;
            _single = 0;
            _double = 0;
            _decimal = 0;
            _short = 0;
            _activeType = typeof(object);

            switch (value.GetType().Name)
            {
                case "Int16":
                    this = new Number((short)value);
                    _activeType = typeof(short);
                    break;
                case "Int32":
                    this = new Number((int)value);
                    _activeType = typeof(int);
                    break;
                case "Int64":
                    this = new Number((long)value);
                    _activeType = typeof(long);
                    break;
                case "Single":
                    this = new Number((float)value);
                    _activeType = typeof(float);
                    break;
                case "Double":
                    this = new Number((double)value);
                    _activeType = typeof(double);
                    break;
                case "Decimal":
                    this = new Number((decimal)value);
                    _activeType = typeof(decimal);
                    break;
                case "String":
                    this = Number.Parse(value.ToString());
                    break;
            }
        }
        #endregion

        #region Standard
        public override string ToString()
        {
            switch (_activeType.Name)
            {
                case "Int16":
                    return _short.ToString();
                case "Int32":
                    return _integer.ToString();
                case "Int64":
                    return _long.ToString();
                case "Single":
                    return _single.ToString();
                case "Double":
                    return _double.ToString();
                case "Decimal":
                    return _decimal.ToString();
            }
            return "";
        }

        public static Number Parse(string value)
        {
            Number number = new Number();
            number.ActiveType = (short.TryParse(value, out number._short)) ? typeof(short) : null;
            if (number.ActiveType == null)
            {
                number.ActiveType = (Int32.TryParse(value, out number._integer)) ? typeof(int) : null;
            }
            if (number.ActiveType == null)
            {
                number.ActiveType = (Int64.TryParse(value, out number._long)) ? typeof(long) : null;
            }
            if (number.ActiveType == null)
            {
                number.ActiveType = (Single.TryParse(value, out number._single)) ? typeof(float) : null;
            }
            if (number.ActiveType == null)
            {
                number.ActiveType = (Double.TryParse(value, out number._double)) ? typeof(double) : null;
            }
            if (number.ActiveType == null)
            {
                number.ActiveType = (Decimal.TryParse(value, out number._decimal)) ? typeof(decimal) : null;
            }

            return number;
        }

        public int CompareTo(object obj)
        {
            Number number = new Number(obj);
            switch (number.ActiveType.Name)
            {
                case "Int16":
                    return Comparer<short>.Default.Compare(number, this);
                case "Int32":
                    return Comparer<int>.Default.Compare(number, this);
                case "Int64":
                    return Comparer<long>.Default.Compare(number, this);
                case "Single":
                    return Comparer<float>.Default.Compare(number, this);
                case "Double":
                    return Comparer<double>.Default.Compare(number, this);
                case "Decimal":
                    return Comparer<decimal>.Default.Compare(number, this);
                default:
                    return 0;
            }
        }

        public override bool Equals(object number)
        {
            return (0 == CompareTo(number));
        }

        public override int GetHashCode()
        {
            switch (this._activeType.Name)
            {
                case "Int16":
                    return this._short.GetHashCode();
                case "Int32":
                    return this._integer.GetHashCode();
                case "Int64":
                    return this._long.GetHashCode();
                case "Single":
                    return this._single.GetHashCode();
                case "Double":
                    return this._double.GetHashCode();
                case "Decimal":
                    return this._decimal.GetHashCode();
                default:
                    return base.GetHashCode();
            }
        }

        #endregion
    }

}